var tree = function(spec) {
	var that = {},
			childName = (spec && spec.childName) || 'items';

	that[childName] = extendArray([]);

	that.append = function(anItem) {
		that[childName].push(anItem);
	};

	var findParent = function(potentialParent, potentialChild) {
		var parent;

		if(potentialParent[childName].contains(potentialChild)) return potentialParent;

		for(var i = 0; i < potentialParent[childName].length; i += 1) {
			if(potentialParent[childName][i][childName]) {
				parent = findParent(potentialParent[childName][i], potentialChild);
				if(parent) return parent;
			}
		}
		return undefined;
	};

	that.findParentOf = function(anItem) {
		return findParent(that, anItem);
	};

	that.move = function(anItem) {
		var parent = that.findParentOf(anItem);

		return {
			up: function() {
				parent[childName] = parent[childName].move(anItem).toBeforeThePreviousItem();
			},
			down: function() {
				parent[childName] = parent[childName].move(anItem).toAfterTheNextItem();
			},
			intoCompositeIfPreviousItemIsComposite: function() {
				var previousItem = parent[childName].findItemPreviousTo(anItem);

				if(previousItem && previousItem[childName]) {
					parent[childName] = parent[childName].remove(anItem);
					previousItem[childName].push(anItem);
				}
			},
			outOfCompositeIfParentIsComposite: function() {
				var grandParent = that.findParentOf(parent);

				if(grandParent) {
					parent[childName] = parent[childName].remove(anItem);
					grandParent[childName] = grandParent[childName].insert(anItem).after(parent);
				}
			}
		};
	};

	that.insert = function(anItem) {
		return {
			before: function(anExistingItem) {
				var parent = that.findParentOf(anExistingItem);
				parent[childName] = parent[childName].insert(anItem).before(anExistingItem); 
			},
			after: function(anExistingItem) {
				var parent = that.findParentOf(anExistingItem);
				parent[childName] = parent[childName].insert(anItem).after(anExistingItem);
			}
		};
	};

	that.replace = function(anExistingItem) {
		return {
			withItem: function(anItem) {
				var parent = that.findParentOf(anExistingItem);
				parent[childName] = parent[childName].replace(anExistingItem).withItem(anItem);
			}
		};
	};

	var remove = function(anItem) {
		var parent = that.findParentOf(anItem);
		parent[childName] = parent[childName].remove(anItem);
	};

	var f = function(anItem, aPredicate, aResultFunction) {
		if(aPredicate(anItem)) aResultFunction(anItem);

		if(anItem[childName]) {
			$.map(anItem[childName], function(aChildItem) { f(aChildItem, aPredicate, aResultFunction); });
		}
	};

	var findAllItemsThatMatch = function(aPredicate) {
		var matches = [];

		f(that, aPredicate, function(anItem) {
			matches.push(anItem);
		});

		return matches;
	};

	that.remove = function(aQuery) {
		if($.isFunction(aQuery)) {
			$.map(findAllItemsThatMatch(aQuery), function(aMatch) { remove(aMatch); });
		} else {
			remove(aQuery);
		}
	};

	return that;
};

var extendArray = function(anArray) {
	var getAnArrayOfItemsPreceding = function(anIndex) {
		if(anIndex < 0) return [];
		return anArray.slice(0, anIndex);
	};

	var getAnArrayOfItemsPrecedingAndIncluding = function(anIndex) {
		if(anIndex < 0) return [];
		return anArray.slice(0, anIndex + 1);
	};

	var getAnArrayOfItemsIncludingAndFollowing = function(anIndex) {
		if(anIndex < 0) return anArray;
		return anArray.slice(anIndex);
	};

	var getAnArrayOfItemsFollowing = function(anIndex) {
		if(anIndex < 0) return anArray;
		return anArray.slice(anIndex + 1);
	};

	anArray.indexOf = function(anItem) {
		for(var i = 0; i < anArray.length; i += 1) {
				if(anArray[i] === anItem) {
						return i;
				}
		}
		return -1;
	};

	anArray.contains = function(anItem) {
		return anArray.indexOf(anItem) !== -1;
	};

	anArray.move = function(anItem) {
		var anItemsIndex = anArray.indexOf(anItem);
		anArray.remove(anItem);

		return {
			toBeforeThePreviousItem: function() {
				var previousIndex = anItemsIndex - 1;
				var begin = getAnArrayOfItemsPreceding(previousIndex);
				var end = getAnArrayOfItemsIncludingAndFollowing(previousIndex);
				return extendArray(begin.concat([anItem].concat(end)));
			},
			toAfterTheNextItem: function() {
				var nextIndex = anItemsIndex + 1;
				var begin = getAnArrayOfItemsPreceding(nextIndex);
				var end = getAnArrayOfItemsIncludingAndFollowing(nextIndex);
				return extendArray(begin.concat([anItem].concat(end)));
			}
		};
	};

	anArray.insert = function(anItem) {
		return {
			before: function(anExistingItem) {
				var theExistingItemsIndex = anArray.indexOf(anExistingItem);
				var begin = getAnArrayOfItemsPreceding(theExistingItemsIndex);
				var end = getAnArrayOfItemsIncludingAndFollowing(theExistingItemsIndex);
				return extendArray(begin.concat([anItem].concat(end)));
			},
			after: function(anExistingItem) {
				var theExistingItemsIndex = anArray.indexOf(anExistingItem);
				var begin = getAnArrayOfItemsPrecedingAndIncluding(theExistingItemsIndex);
				var end = getAnArrayOfItemsFollowing(theExistingItemsIndex);
				return extendArray(begin.concat([anItem].concat(end)));
			}
		};
	};

	anArray.replace = function(anExistingItem) {
		return {
			withItem: function(anItem) {
				var theExistingItemsIndex = anArray.indexOf(anExistingItem);
				anArray.remove(anExistingItem);
				var begin = getAnArrayOfItemsPreceding(theExistingItemsIndex);
				var end = getAnArrayOfItemsIncludingAndFollowing(theExistingItemsIndex);
				return extendArray(begin.concat([anItem].concat(end)));
			}
		};
	};

	anArray.findItemPreviousTo = function(anItem) {
		var anItemsIndex = anArray.indexOf(anItem);
		return anArray[anItemsIndex - 1];
	};

	anArray.remove = function(anItem) {
		anArray.splice(anArray.indexOf(anItem), 1);
		return extendArray(anArray);
	};

	return anArray;
};